package com.boot2.core.dao.jpa;

import com.boot2.core.dao.DaoUtil;
import com.boot2.core.dict.PersistentEnum;
import com.boot2.core.utils.BeanUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author zhangweilin
 * @ClassName: AbsctractDao
 * @description: 抽象jpa dao,提取一些公共方法,尤其是动态查询语句
 * @date 2017年12月27日 下午7:16:26
 */
//@CacheConfig(cacheNames = {"model:AbsctractDao"})
@NoRepositoryBean
public interface AbsctractDao<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {


    default Specification<T> getSpecification(Map<String, Object> paramMap) {
        return (Specification<T>) DaoUtil.buildCriteria(PersistentEnum.jpa, paramMap);
    }

    /**
     * 获取Specification，凑多条件查询
     *
     * @param t
     * @return
     */
    default Specification<T> getSpecification(T t) {
        return (Specification<T>) DaoUtil.buildCriteria(PersistentEnum.jpa, t);

    }

    /**
     * Returns a single entity matching the given {@link Specification}.
     *
     * @param t
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default T findOne(T t) {
        Specification<T> specification = getSpecification(t);
        Optional<T> optional = findOne(specification);
        return optional.orElse(null);
    }

    @Override
    default T getOne(ID id) {
        Optional<T> optional = findById(id);
        return optional.orElse(null);
    }

    /**
     * Returns a single entity matching the given {@link Specification}.
     *
     * @param paramMap
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default T findOne(Map<String, Object> paramMap) {
        Specification<T> specification = getSpecification(paramMap);
        return findOne(specification).get();
    }

    /**
     * Returns all entities matching the given {@link Specification}.
     *
     * @param t
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default List<T> findAll(T t) {
        Specification<T> specification = getSpecification(t);
        return findAll(specification);
    }

    //    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default List<T> findAll(Map<String, Object> paramMap) {
        Specification<T> specification = getSpecification(paramMap);
        return findAll(specification);
    }

    /**
     * Returns a {@link Page} of entities matching the given {@link Specification}.
     *
     * @param t
     * @param pageable
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0+#p1",sync = true)
    default Page<T> findAll(T t, Pageable pageable) {
        Specification<T> specification = getSpecification(t);
        return findAll(specification, pageable);
    }

    /**
     * Returns a {@link Page} of entities matching the given {@link Specification}.
     *
     * @param paramMap
     * @param pageable
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0+#p1",sync = true)
    default Page<T> findAll(Map<String, Object> paramMap, Pageable pageable) {
        Specification<T> specification = getSpecification(paramMap);
        return findAll(specification, pageable);
    }

    /**
     * Returns all entities matching the given {@link Specification} and
     * {@link Sort}.
     *
     * @param t
     * @param sort
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0+#p1",sync = true)
    default List<T> findAll(T t, Sort sort) {
        Specification<T> specification = getSpecification(t);
        return findAll(specification, sort);
    }

    /**
     * Returns all entities matching the given {@link Specification} and
     * {@link Sort}.
     *
     * @param paramMap
     * @param sort
     * @return
     */
//    @Cacheable(key = "targetClass+methodName +#p0+#p1",sync = true)
    default List<T> findAll(Map<String, Object> paramMap, Sort sort) {
        Specification<T> specification = getSpecification(paramMap);
        return findAll(specification, sort);
    }

    /**
     * Returns the number of instances that the given {@link Specification} will
     * return.
     *
     * @param t the {@link Specification} to count instances for
     * @return the number of instances
     */
//    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default long count(T t) {
        Specification<T> specification = getSpecification(t);
        return count(specification);
    }

    /**
     * Returns the number of instances that the given {@link Specification} will
     * return.
     *
     * @param paramMap the {@link Specification} to count instances for
     * @return the number of instances
     */
//    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default long count(Map<String, Object> paramMap) {
        Specification<T> specification = getSpecification(paramMap);
        return count(specification);
    }

    /**
     * Saves an entity and flushes changes instantly.
     * 如果是自己查询过，请直接调用saveAndFlush,如果没查过，且想更新，才调此方法
     *
     * @param entity
     * @return the saved entity
     */
//    @Cacheable(key = "targetClass+methodName +#p0",sync = true)
    default <S extends T> S saveAndUpdateAndFlush(S entity) {
        try {
            ID id = (ID) MethodUtils.invokeExactMethod(entity, "getId");
            // 为空，直接增加
            if (null == id) {
                return saveAndFlush(entity);
            } else {
                // 先查后更新
                T t = findById(id).get();
                BeanUtils.copyProperties(entity, t);
                return (S) saveAndFlush(t);
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            return saveAndFlush(entity);
        }
    }
}
